<?php

/** 
 * @Author: 李红雨 - RainLee <rainlee1990@yeah.net>
 * @Date: 2022-06-21 14:12:50
 * @LastEditors: 李红雨 - RainLee <rainlee1990@yeah.net>
 * @LastEditTime: 2022-07-16 20:13:38
 * @Description: File Description
 */

namespace rainlee\authn\guard;

use rainlee\authn\NodeDrive;
use rainlee\authn\provider\CreateUserProviders;
use rainlee\authn\Authenticatable;
use rainlee\authn\jwt\exception\ClaimException;
use rainlee\authn\jwt\JwtHandler;

class JwtGuard
{
    use CreateUserProviders;

    protected $guard;

    protected $provider = null;

    protected $node_drive = null;

    protected $user = null;

    protected $jwt;

    public function __construct($name, $config)
    {
        $this->guard      = $name;
        $this->jwt        = new JwtHandler($name, $config['jwt']);
        $this->node_drive = new NodeDrive($config['node_drive']);
        $this->provider   = $this->createUserProviders($config['provider']);
    }

    /**
     * 尝试登录
     *
     * @param  array    $credentials
     * @return bool
     */
    public function attempt(array $credentials = [])
    {
        $this->lastAttempted = $user = $this->provider->retrieveByCredentials($credentials);
        if (!is_null($user) && $this->hasValidCredentials($user, $credentials)) {
            $this->setUser($user);
            return true;
        }

        return false;
    }

    /**
     * 尝试通过ID登录
     * 
     * @param int      $id
     * @return bool
     */
    public function attemptById($id)
    {
        if (!is_null($user = $this->provider->retrieveById($id))) {
            $this->setUser($user);
            return true;
        }
        return false;
    }

    /**
     * 验证参数有效性
     * 
     * @param \rainlee\auth\Authenticatable|null $user 用户对象
     * @param array $credentials 参数
     * @return bool
     */
    protected function hasValidCredentials($user, $credentials)
    {
        return !is_null($user) && $this->provider->validatePassword($user, $credentials['password']);
    }

    /**
     * 生成 Token 
     * 
     * @param array $claims 荷载数据
     * @return string
     */
    public function build($claims = [])
    {
        $payload_keys = $this->jwt->getPayloadKeys();

        if (!in_array($this->user->getPk(), $payload_keys) && !in_array($this->user->getPk(), array_keys($claims))) {
            throw new ClaimException('The primary key must exist');
        }

        $payload = array_filter($this->user->toArray(), function ($key) use ($payload_keys) {
            return in_array($key, $payload_keys);
        }, ARRAY_FILTER_USE_KEY);
        $claims = array_merge($payload, $claims);
        return $this->jwt->build($claims);
    }

    /**
     * 获取用户信息
     */
    public function user()
    {
        // 用户存在则只接返回用户信息
        if (!is_null($this->user)) {
            return $this->user;
        }

        // 获取用户主键
        $pk = $this->provider->getPk();
        $id = $this->jwt->getPayload()->data->$pk;

        if ($id) {
            return $this->provider->retrieveById($id);
        }

        return null;
    }

    /**
     * 退出登录
     */
    public function logout()
    {
        return $this->jwt->blacklist();
    }

    /**
     * 验证登录状态
     * @param string $token
     * @return bool
     */
    public function check($token = null)
    {
        if ($this->node_drive->ignored()) {
            return true;
        }

        $this->jwt->validation($token);

        return true;
    }

    /**
     * 刷新 Token
     * 
     * @param string $token
     * @return string
     */
    public function refresh($token = null)
    {
        return $this->jwt->refresh($token);
    }

    /**
     * 设置当前用户
     * 
     * @param \rainlee\authn\Authenticatable $user
     * @return void
     */
    protected function setUser(Authenticatable $user)
    {
        $this->user = $user;
    }
}
